package com.hb.repository.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;

import com.hb.repository.SpitterRepository;
import com.hb.domain.Spitter;
import org.springframework.stereotype.Repository;

import javax.sql.DataSource;

@Repository
public class JdbcSpitterRepository implements SpitterRepository {
    /**
     * JdbcOperations 是一个接口，定义了 JdbcTemplate 所实现的操作。通过注入 JdbcOperations，而不是具体的 JdbcTemplate，能够保证 JdbcSpitterRepository 通
     * 过 JdbcOperations 接口达到与 JdbcTemplate 保持松耦合。
     */
    private JdbcOperations jdbcOperations;

    private NamedParameterJdbcOperations namedParameterJdbcOperations;

    @Autowired
    public JdbcSpitterRepository(JdbcOperations jdbcOperations, NamedParameterJdbcOperations namedParameterJdbcOperations) {
        this.jdbcOperations = jdbcOperations;
        this.namedParameterJdbcOperations = namedParameterJdbcOperations;
    }

    @Override
    public long count() {
        return 0;
    }

    @Override
    public Spitter save(Spitter spitter) {
        return null;
    }

    private long insertSpitterAndReturnId(Spitter spitter) {
        return 0;
    }

    @SuppressWarnings("unused")
    private void insertSpitter(Spitter spitter) {
    }

    @Override
    public Spitter findOne(long id) {
        return null;
    }

    @Override
    public Spitter findByUsername(String username) {
        return null;
    }

    @Override
    public List<Spitter> findAll() {
        return null;
    }

    private static final class SpitterRowMapper implements RowMapper<Spitter> {
        @Override
        public Spitter mapRow(ResultSet rs, int rowNum) throws SQLException {
            long id = rs.getLong("id");
            String username = rs.getString("username");
            String password = rs.getString("password");
            String fullName = rs.getString("fullname");
            String email = rs.getString("email");
            boolean updateByEmail = rs.getBoolean("updateByEmail");
            return new Spitter(id, username, password, fullName, email, updateByEmail);
        }
    }

    private static final String INSERT_SPITTER = "insert into Spitter (username, password, fullname, email, updateByEmail) values (?, ?, ?, ?, ?)";

    private static final String SELECT_SPITTER = "select id, username, password, fullname, email, updateByEmail from Spitter";

    private static final String SQL_INSERT_SPITTER = "insert into Spitter(username, password, fullname) values (?, ?, ?)";

    /**
     * 除了这种方法之外，我们还可以使用命名参数。命名参数可以赋予 SQL 中的每个参数一个明确的名字，在绑定值到查询语句的时候就通过该名字来引用参数。
     * 使用命名参数查询，绑定值的顺序就不重要了，我们可以按照名字来绑定值。如果查询语句发生了变化导致参数的顺序与之前不一致，我们不需要修改绑定的代码。
     */
    private static final String SQL_INSERT_SPITTER_NAMEDPARAMETER = "insert into Spitter(username, password, fullname) values (:username, :password, :fullname)";

    private DataSource dataSource;

    /**
     * 使用 JDBC 原始 API 往数据库中插入数据
     * 只有几行代码是真正用于进行插入数据的。但是 JDBC 要求你必须正确地管理连接和语句，并以某种方式处理可能抛出的 SQLException 异常。
     * 再提一句这个 SQLException 异常：你不但不清楚如何处理它（因为并不知道哪里出错了），而且你还要捕捉它两次！你需要在插入记录出错时捕捉它，同时你还需要在
     * 关闭语句和连接出错的时候捕捉它。看起来我们要做很多的工作来处理可能出现的问题，而这些问题通常是难以通过编码来处理的。
     *
     * @param spitter
     */
    public void addSpitter(Spitter spitter) {
        Connection conn = null;
        PreparedStatement stmt = null;
        try {
            // 获取连接
            conn = dataSource.getConnection();
            // 创建语句
            stmt = conn.prepareStatement(SQL_INSERT_SPITTER);
            // 绑定参数
            stmt.setString(1, spitter.getUsername());
            stmt.setString(2, spitter.getPassword());
            stmt.setString(3, spitter.getFullName());
            // 执行语句
            stmt.execute();
        } catch (SQLException e) {

        } finally {
            try {
                if (stmt != null) {
                    // 关闭释放资源
                    stmt.close();
                }
                if (conn != null) {
                    // 关闭释放资源
                    conn.close();
                }
            } catch (SQLException e) {

            }
        }
    }

    /**
     * 基于 JdbcTemplate 的 addSpitter() 方法
     * 这里没有了创建连接和语句的代码，也没有异常处理的代码，只剩下单纯的数据插入代码。不能因为你看不到这些样板代码，就意味着它们不存在。样板代码被
     * 巧妙地隐藏到 JDBC 模板类中了。在这里，你也看不到对 SQLException 处理的代码。在内部，JdbcTemplate 将会捕获所有可能抛出的 SQLException，并将通
     * 用的 SQLException 转换为那些更明确的数据访问异常，然后将其重新抛出。因为 Spring 的数据访问异常都是运行时异常，所以我们不必在方法中进行捕获。
     *
     * @param spitter
     */
    public void addSpitterJdbcTemplate(Spitter spitter) {
        jdbcOperations.update(INSERT_SPITTER, spitter.getUsername(), spitter.getPassword(), spitter.getFullName());
    }

    /**
     * 使用命名参数的形式插入数据，命名参数是通过 java.util.Map 来进行绑定的。
     *
     * @param spitter
     */
    public void addSpitterNamedParameter(Spitter spitter) {
        Map<String, Object> paramMap = new HashMap<String, Object>();
        paramMap.put("username", spitter.getUsername());
        paramMap.put("password", spitter.getPassword());
        paramMap.put("fullname", spitter.getFullName());
        // 执行数据插入
        namedParameterJdbcOperations.update(SQL_INSERT_SPITTER_NAMEDPARAMETER, paramMap);
    }

    private static final String SQL_UPDATE_SPITTER = "update spitter set username = ?, password = ?, fullname = ? where id = ?";

    /**
     * 使用 JDBC 更新数据库中的一行
     *
     * @param spitter
     */
    public void saveSpitter(Spitter spitter) {
        Connection conn = null;
        PreparedStatement stmt = null;
        try {
            // 获取连接
            conn = dataSource.getConnection();
            // 创建语句
            stmt = conn.prepareStatement(SQL_UPDATE_SPITTER);
            // 绑定参数
            stmt.setString(1, spitter.getUsername());
            stmt.setString(2, spitter.getPassword());
            stmt.setString(3, spitter.getFullName());
            stmt.setLong(4, spitter.getId());
            // 执行语句
            stmt.execute();
        } catch (SQLException e) {

        } finally {
            try {
                if (stmt != null) {
                    // 关闭释放资源
                    stmt.close();
                }
                if (conn != null) {
                    // 关闭释放资源
                    conn.close();
                }
            } catch (SQLException e) {

            }
        }
    }

    private static final String SQL_SELECT_SPITTER = "select id, username, fullname from spitter where id = ?";

    public Spitter findOneJdbc(long id) {
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;
        try {
            // 获取连接
            conn = dataSource.getConnection();
            // 创建语句
            stmt = conn.prepareStatement(SQL_SELECT_SPITTER);
            // 绑定参数
            stmt.setLong(1, id);
            // 执行语句
            rs = stmt.executeQuery();
            Spitter spitter = null;
            // 处理结果
            if (rs.next()) {
                spitter = new Spitter();
                spitter.setId(rs.getLong("id"));
                spitter.setUsername(rs.getString("username"));
                spitter.setPassword(rs.getString("password"));
                spitter.setFullName(rs.getString("fullname"));
            }
            return spitter;
        } catch (SQLException e) {

        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (stmt != null) {
                    // 关闭释放资源
                    stmt.close();
                }
                if (conn != null) {
                    // 关闭释放资源
                    conn.close();
                }
                return null;
            } catch (SQLException e) {
                return null;
            }
        }
    }

    /**
     * 在这个方法中使用了 JdbcTemplate 的 queryForObject() 方法来从数据库查询Spitter。queryForObject() 方法有三个参数：
     * 1、String 对象，包含了要从数据库中查找数据的 SQL；
     * 2、RowMapper 对象，用来从 ResultSet 中提取数据并构建域对象（本例中为 Spitter）；
     * 3、可变参数列表，列出了要绑定到查询上的索引参数值。
     * 真正奇妙的事情发生在 SpitterRowMapper 对象中，它实现了 RowMapper 接口。对于查询返回的每一行数据，JdbcTemplate 将会调用 RowMapper的mapRow() 方法，
     * 并传入一个 ResultSet 和包含行号的整数。在 SpitterRowMapper 的 mapRow() 方法中，我们创建了 Spitter 对象并将 ResultSet 中的值填充进去。
     * 这个方法也不用写 JDBC 模板代码。不同于传统的 JDBC，这里没有资源管理或者异常处理代码。使用 JdbcTemplate 的方法只需关注于如何从数据库中获取 Spitter 对象即可。
     *
     * @param id
     * @return
     */
    public Spitter findOneJdbcTemplate(long id) {
        return jdbcOperations.queryForObject(SELECT_SPITTER, new SpitterRowMapper(), id);
    }

    /**
     * 因为 RowMapper 接口只声明了 mapRow() 这一个方法，因此它完全符合函数式接口（functional interface）的标准（有且只有一个方法的接口）。这意味着如果使用
     * Java 8 来开发应用的话，我们可以使用 Lambda 来表达 RowMapper 的实现，而不必再使用具体的实现类了。
     * @param id
     * @return
     */
    public Spitter findOneInJava8Lambda(long id) {
        return jdbcOperations.queryForObject(SELECT_SPITTER,
                (rs, rowNum) -> {
                    return new Spitter(
                            rs.getLong("id"),
                            rs.getString("username"),
                            rs.getString("password"),
                            rs.getString("fullname"),
                            rs.getString("email"),
                            rs.getBoolean("updateByEmail")
                    );
                }, id);
    }

    /**
     * 我们还可以使用 Java 8 的方法引用，在单独的方法中定义映射逻辑：
     * @param id
     * @return
     */
    public Spitter findOneJava8MethodRef(long id) {
        return jdbcOperations.queryForObject(SELECT_SPITTER, this::mapSpitter, id);
    }

    private Spitter mapSpitter(ResultSet rs, int row) throws SQLException {
        return new Spitter(
                rs.getLong("id"),
                rs.getString("username"),
                rs.getString("password"),
                rs.getString("fullname"),
                rs.getString("email"),
                rs.getBoolean("updateByEmail"));
    }
}
